package week7.jdbc;

import java.sql.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class DbUtil {

    private static ThreadLocal<Stack<String>> threadLocal = ThreadLocal.withInitial(Stack::new);

    private static ConcurrentHashMap<String, ConnectionFactory> factoryConcurrentHashMap = new ConcurrentHashMap<>();

    static {

        registerDataSource("shopping", new JDBCFactory());
        registerDataSource("shopping-2", new HikariFactory());

    }

    public static void registerDataSource(String name, ConnectionFactory factory) {
        if (!factoryConcurrentHashMap.containsKey(name)) {
            factoryConcurrentHashMap.put(name, factory);
        } else {
            throw new RuntimeException("datasource " + name + " already exist");
        }
    }

    /**
     * 查询
     *
     * @param sql
     * @param args
     * @return
     */
    public static List<Map<String, Object>> executeQuery(String sql, Object... args) {
        return executeQuery(sql, Arrays.asList(args));
    }

    /**
     * 查询
     *
     * @param sql
     * @param args
     * @return
     */
    public static List<Map<String, Object>> executeQuery(String sql, List<Object> args) {
        //获取当前选择的执行器
        Stack<String> stack = threadLocal.get();
        if (stack.size() <= 0) {
            throw new RuntimeException("datasource不能为空");
        }

        String name = stack.peek();
        Connection conn = factoryConcurrentHashMap.get(name).getConnection();

        return executeQuery(conn, sql, args);
    }

    private static List<Map<String, Object>> executeQuery(Connection conn, String sql, List<Object> args) {

        PreparedStatement st = null;
        ResultSet rs = null;

        List<Map<String, Object>> result = new ArrayList<>();

        try {

            //获得语句执行者
            st = conn.prepareStatement(sql);

            int i = 1;

            for (Object o : args) {
                st.setObject(i++, o);
            }

            rs = st.executeQuery();

            ResultSetMetaData metaData = rs.getMetaData();
            int size = metaData.getColumnCount();

            List<String> columns = new ArrayList<>(size);

            for (i = 1; i <= size; i++) {
                columns.add(metaData.getColumnLabel(i));
            }

            while (rs.next()) {
                Map<String, Object> row = new HashMap<>();
                for (String c : columns) {
                    row.put(c, rs.getObject(c));
                }
                result.add(row);
            }


        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            closeResource(conn, st, rs);
        }

        return result;

    }

    /**
     * 执行更新
     *
     * @param sql
     * @param args
     * @return
     */
    public static int executeUpdate(String sql, Object... args) {
        return executeUpdate(sql, Arrays.asList(args));
    }

    /**
     * 执行更新
     *
     * @param sql
     * @param args
     * @return
     */
    public static int executeUpdate(String sql, List<Object> args) {
        //获取当前选择的执行器
        Stack<String> stack = threadLocal.get();
        if (stack.size() <= 0) {
            throw new RuntimeException("datasource不能为空");
        }

        String name = stack.peek();
        Connection conn = factoryConcurrentHashMap.get(name).getConnection();

        return executeUpdate(conn, sql, args);
    }

    private static int executeUpdate(Connection conn, String sql, List<Object> args) {
        PreparedStatement st = null;
        int result = 0;

        try {

            //获得语句执行者
            st = conn.prepareStatement(sql);

            int i = 1;

            for (Object o : args) {
                st.setObject(i++, o);
            }

            result = st.executeUpdate();

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            closeResource(conn, st, null);
        }

        return result;
    }

    /**
     * 批量插入
     *
     * @param sql
     * @param args
     * @return
     */
    public static int[] executeInsertBatch(String sql, List<List<Object>> args) {

        //获取当前选择的执行器
        Stack<String> stack = threadLocal.get();
        if (stack.size() <= 0) {
            throw new RuntimeException("datasource不能为空");
        }

        String name = stack.peek();
        Connection conn = factoryConcurrentHashMap.get(name).getConnection();

        return executeInsertBatch(conn, sql, args);
    }

    private static int[] executeInsertBatch(Connection conn, String sql, List<List<Object>> args) {

        PreparedStatement st = null;
        int[] result = null;

        try {

            st = conn.prepareStatement(sql);

            for (int k = 0; k < args.size(); k++) {

                int i = 1;

                List<Object> row = args.get(k);

                for (Object o : row) {
                    st.setObject(i++, o);
                }

                st.addBatch();

            }

            result = st.executeBatch();

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            closeResource(conn, st, null);
        }

        return result;
    }

    public static void use(String name, DbCall call) {

        if (null == name || name.trim().equals("")) {
            throw new RuntimeException("name 不能为空");
        }

        if (!factoryConcurrentHashMap.containsKey(name)) {
            throw new RuntimeException("datasource " + name + " not exist");
        }

        Stack<String> stack = threadLocal.get();

        if (stack.size() > 0 && name.equals(stack.peek())) {
            call.call();
        } else {
            stack.push(name);
            try {
                call.call();
            } finally {
                stack.pop();
            }
        }
    }

    //释放资源
    private static void closeResource(Connection conn, Statement st, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {

            }
        }

        if (st != null) {
            try {
                st.close();
            } catch (SQLException e) {
            }
        }

        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
            }
        }
    }


}
